<?php

namespace app\admin\service\dev;

use app\admin\model\dev\Action as ActionModel;
use app\admin\model\dev\Field as FieldModel;
use app\admin\model\dev\Api as ApiModel;
use app\admin\service\generate\Generate;
use app\admin\validate\dev\Api as ApiValidate;
use app\admin\service\admin\I18nService;
use app\common\Constants;
use app\common\exception\BusinessException;
use app\common\facade\StrHelper;
use app\common\service\ApiClientService;
use think\facade\Db;
use think\facade\Log;

class ApiService
{
    public static function genCodeHandle($menu_id, $overwrite = false): array
    {
        $app_root_path = app()->getRootPath();
        $menuData = MenuService::prepareMenuData($menu_id);
        $data = json_encode($menuData);
        $appId = config('rds.gen_code.app_id');
        $appSecret = config('rds.gen_code.app_secret');
        $baseUrl = config('rds.gen_code.base_url');
        $client = new ApiClientService($appId, $appSecret, $baseUrl);
        $path = '/api/basic.generater/gen';
        $response = $client->post($path, $data);
        if ($response['code'] != 200) {
            throw new BusinessException($response['message']);
        }
        // 写入文件
        foreach ($response['data'] as $code) {
            self::writeFile($app_root_path . $code['file'], $code['code'], $overwrite, $code['once']);
        }
        return [];
    }

    private static function writeFile($filepath, $content, $overwrite = true, $once = false): void
    {
        ob_start();
        echo $content;
        $_cache = ob_get_contents();
        ob_end_clean();

        $filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath);
        $filepath = str_replace('\\', DIRECTORY_SEPARATOR, $filepath);

        trace($filepath, 'WriteFile');

        if ($_cache) {
            // 如果文件存在
            if (file_exists($filepath)) {
                trace('文件已存在', 'WriteFile');
                // 不是仅生成一次
                if (!$once) {
                    trace('每次都生成', 'WriteFile');
                    // 需要备份旧文件
                    if (!$overwrite) {
                        trace('生成备份文件', 'WriteFile');
                        $backupFilepath = $filepath . date('YmdHis');
                        if (!rename($filepath, $backupFilepath)) {
                            trace('Failed to backup file: ' . $filepath . ' to ' . $backupFilepath, 'error');
                        }
                    }
                } else {
                    // 仅生成一次
                    trace('仅生成一次，跳过', 'WriteFile');
                    return;
                }
            }
            $file = new \think\template\driver\File();
            $file->write($filepath, $_cache);
        }
    }

    public static function addApi(array $post)
    {
        try {
            // 验证
            validate(ApiValidate::class)->scene('add')->check($post);
            // 预处理菜单数据
            $menuData = self::transferMenuData($post);
            // 开启总事务
            Db::startTrans();
            // 如果创建数据库表 且 类型为菜单时
            if ($post['gen_table'] === 1 && $menuData['type'] == Constants::MENU_TYPE_MENU) {
                try {
                    if (DatabaseService::hasTable($menuData['table_name'], $menuData['connect'])) {
                        // 存在表则先重命名表，然后重新创建
                        DatabaseService::renameTable($menuData, date("YmdHis"));
                    }
                    DatabaseService::syncDatabaseTable($menuData);
                    $defaultFields = self::getDefaultCreateFields($menuData);
                    foreach ($defaultFields as $field) {
                        // 依次创建默认字段的物理列
                        DatabaseService::createOrUpdateTableField($menuData, $menuData['primary_key'], $field);
                    }
                } catch (\Exception $e) {
                    DatabaseService::renameTable($menuData, date("YmdHis"));
                    throw $e;
                }
                $menuData['table_exist'] = Constants::MENU_TABLE_EXIST;
            }
            // 创建菜单
            $menuInfo = ApiModel::create($menuData);
            // 如果创建数据库表 且 类型为菜单时
            if ($post['gen_table'] === 1 && $menuInfo['type'] == Constants::MENU_TYPE_MENU) {
                self::addDefaultFields($menuInfo);
                self::addDefaultActions($menuInfo);
            }
            // 提交总事务
            Db::commit();
            // 返回结果
            return $menuInfo['id'];
        } catch (\Exception $e) {
            // 回退事务
            Db::rollback();
            throw $e;
        }
    }


    public static function updateApi(array $post)
    {
        try {
            // 验证
            validate(ApiValidate::class)->scene('update')->check($post);
            // 获取更新前的菜单信息
            $oldMenuInfo = ApiModel::findOrFail($post['id'])->toArray();
            // 预处理菜单数据
            $menuData = self::transferMenuData($post);
            // 开启总事务
            Db::startTrans();
            // 如果已经创建了数据库表，则同步数据库表结构（重命名表）
            if ($post['gen_table'] === 1 && $menuData['type'] == Constants::MENU_TYPE_MENU) {
                DatabaseService::syncDatabaseTable($menuData, $oldMenuInfo);
            }
            // 更新菜单元数据
            ApiModel::update($menuData, ['id' => $menuData['id']]);
            // 如果主键发生变化，需要同步更新字段表中的记录
            if (
                $post['gen_table'] === 1 &&
                $menuData['type'] == Constants::MENU_TYPE_MENU &&
                $menuData['primary_key'] !== $oldMenuInfo['primary_key']
            ) {
                self::updatePrimaryKeyField($menuData, $oldMenuInfo);
            }
            // 如果控制器发生变化，则修改对应的方法的 url
            // 注意与admin的区别
            if ($menuData['controller'] !== $oldMenuInfo['controller']) {
                $menuNames = StrHelper::getMenuNames($menuData['controller']);
                $actionList = Db::name('dev_action')->where('menu_id', $menuData['id'])->select();
                foreach ($actionList as $action) {
                    Db::name('dev_action')
                        ->where('id', $action['id'])
                        ->update([
                            'url' => $menuNames['url_prefix'] . '/' . $action['method']
                        ]);
                }
            }
            // 提交总事务
            Db::commit();
            // 返回结果
            return true;
        } catch (\Exception $e) {
            // 回退事务
            Db::rollback();
            throw $e;
        }
    }


    public static function deleteApi(int $id)
    {
        try {
            $menu_info = ApiModel::find($id);

            // 如果已经创建了数据库表，则需要改名备份数据库表
            if ($menu_info['gen_table'] === 1 && $menu_info['type'] === Constants::MENU_TYPE_MENU) {
                $timestamp = date("YmdHis");
                try {
                    DatabaseService::renameTable($menu_info, $timestamp);
                } catch (\Exception $e) {
                    DatabaseService::renameTable($menu_info, $timestamp, true);
                    throw new BusinessException(Constants::E_SYS_DATABASE_ERROR);
                }
            }

            // 开启事务
            Db::startTrans();

            // 删除字段信息
            FieldService::deleteMenuFields($menu_info['id']);
            // 删除方法信息
            ActionService::deleteAction($menu_info['id']);
            // 删除菜单信息
            ApiModel::destroy(['id' => $menu_info['id']]);

            // 提交事务
            Db::commit();

            // 删除相关文件
            self::deleteMenuFiles($menu_info);
        } catch (\Exception $e) {
            // 回退事务
            Db::rollback();
            // 恢复重命名
            DatabaseService::renameTable($menu_info, $timestamp, true);
            trace([
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'error_message' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ], 'debug');
            throw $e;
        }
    }


    /**
     * 获得菜单默认创建的字段信息
     * 根据这些信息向数据库的表中添加字段
     * 包含基于管理目的内置字段
     * @return array
     */
    public static function getDefaultCreateFields($menuInfo): array
    {
        try {
            $default_field_list = Db::name('dev_prop')
                ->json(['config'])
                ->where('type', '=', 'default_field')
                ->where('for_api', '=', 1) // API菜单
                ->where('for_template', '=', 'table') // 综合表格模板
                ->column('config');
            $buildin_field_list = config('field');
            $field_list = array_merge($default_field_list, $buildin_field_list);
            $first_key = array_key_first($field_list);
            $first_element = [$first_key => $field_list[$first_key]];
            $remaining_elements = array_slice($field_list, 1, null, true);
            $reversed_remaining = array_reverse($remaining_elements, true);
            $result_array = $first_element + $reversed_remaining;
            foreach ($result_array as $key => $value) {
                $default_create_fields[$key] = [
                    'field' => $value['field'],
                    'field_type' => $value['config']['field_type'],
                    'field_length' => $value['config']['field_length'],
                    'field_index' => $value['config']['field_index'],
                    'field_constraint' => $value['config']['field_constraint'],
                    'field_default' => $value['config']['field_default'],
                ];
            }
            trace($default_create_fields, '默认创建的数据库表的字段');
            return $default_create_fields;
        } catch (\Exception $e) {
            throw $e;
        }
    }


    /**
     * 为菜单创建默认字段信息（dev_field）
     * @param $menu
     * @return true|void
     * @throws \Exception
     */
    public static function addDefaultFields($menu)
    {
        try {
            // 只有业务菜单才可以自动生成默认字段
            if ($menu['type'] === Constants::MENU_TYPE_MENU) {
                // 查询类型为【默认字段】，适用模板为【菜单模板】的所有默认菜单
                $default_field_list = Db::name('dev_prop')
                    ->json(['config'])
                    ->where('type', '=', 'default_field')
                    ->where('for_api', '=', 1) // API菜单
                    ->where('for_template', '=', 'table') // 综合表格模板
                    ->column('config');
                // 为字段设置所属菜单
                foreach ($default_field_list as &$field) {
                    $field["menu_id"] = $menu['id'];
                    // 内置的字段都有预设翻译数据
                    $field['key'] = 'common.field.' . $field['field'];
                    // 内置方法都设置为 buildin = 1
                    $field['buildin'] = 1;
                }
                // 解除引用
                unset($field);
                // 为菜单添加默认字段信息
                (new FieldModel())->saveAll($default_field_list);
                return true;
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * 添加默认字段
     * @param $menu
     * @return true|void
     * @throws \Exception
     */
    public static function addDefaultActions($menu)
    {
        try {
            $menuNames = StrHelper::getMenuNames($menu['controller']);
            // 只有业务菜单才可以自动生成默认字段
            if ($menu['type'] === Constants::MENU_TYPE_MENU) {
                // 查询类型为【默认方法】，适用模板为【菜单模板】的所有默认菜单
                $default_action_list = Db::name('dev_prop')
                    ->json(['config'])
                    ->where('type', '=', 'default_action')
                    ->where('for_api', '=', '1') // 后台菜单
                    ->where('for_template', '=', 'table')
                    ->column('config');
                foreach ($default_action_list as &$action) {
                    $action['app'] = 'api';
                    $method = $action['method'];
                    $action["menu_id"] = $menu['id'];
                    $action['code'] = null;
                    $action['url'] = $menuNames['url_prefix'] . '/' . $method;
                    // 内置的方法都有预设翻译数据
                    // $action['key'] = 'common.action.' . $method;
                    $action['key'] = null;
                    // 内置方法都设置为 buildin = 1
                    $action['buildin'] = 1;
                }
                unset($action); // 解除引用
                (new ActionModel())->saveAll($default_action_list);
                return true;
            }
        } catch (\Exception $e) {
            throw $e;
        }
    }


    /**
     * 根据控制器名称转换成语言前缀
     * @param $controller
     * @return string
     * @throws \Exception
     * @example menu.demo.my.my-date
     */
    public static function convertToLanguagePrefix($controller): string
    {
        try {
            $parts = explode('/', $controller);
            $convertedParts = [];
            foreach ($parts as $part) {
                $converted = preg_replace('/(?<!^)([A-Z])/', '-$1', $part);
                $converted = strtolower($converted);
                $convertedParts[] = $converted;
            }
            $keyword = implode('.', $convertedParts);
            return 'api.' . $keyword;
        } catch (\Exception $e) {
            trace([
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'error_message' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ], 'debug');
            throw $e;
        }
    }


    /**
     * 将菜单的控制器按照规则转换为此菜单所有方法的权限前缀
     * @return string
     * @example 输入：'Demo/My/MyDate' => 'demo:my:my-date'
     */
    public static function convertToPermissionPrefix($controller): string
    {
        try {
            $parts = explode('/', $controller);
            $convertedParts = [];
            foreach ($parts as $part) {
                $converted = preg_replace('/(?<!^)([A-Z])/', '-$1', $part);
                $converted = strtolower($converted);
                $convertedParts[] = $converted;
            }
            return implode(':', $convertedParts);
        } catch (\Exception $e) {
            trace([
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'error_message' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ], 'debug');
            throw $e;
        }
    }


    /**
     * 将提交的数据格式化为可以直接存储到数据库的数据
     * @param $data
     * @return mixed
     * @throws \Exception
     */
    public static function transferMenuData($data): mixed
    {
        try {

            // 类型为菜单
            if ($data['type'] === Constants::MENU_TYPE_MENU) {
                $hasTable = DatabaseService::hasTable($data['table_name'],$data['connect']);
                $menuNames = StrHelper::getMenuNames($data['controller']);
                $data['primary_key'] = $data['primary_key'] ?: 'id';
                $data['route_name'] = $menuNames['route_name'];
                $data['route_path'] = $menuNames['route_path'];
                $data['route_path_full'] = $menuNames['route_path_full'];
                $data['table_exist'] = $hasTable ? Constants::MENU_TABLE_EXIST : Constants::MENU_TABLE_NOT_EXIST; // 默认未创建表
            }
            // 类型为目录
            if ($data['type'] === Constants::MENU_TYPE_CATALOG) {
                $data['connect'] = null;
                $data['table_name'] = null;
                $data['primary_key'] = null;
            }
            $data['is_sys'] = 0;
            $data['permission'] = 0;
        } catch (\Exception $e) {
            trace([
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'error_message' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ], 'debug');
            throw $e;
        }
        return $data;
    }

    /******************************************************************************************************************/


    /**
     * 删除菜单文件
     * @param $menuInfo
     * @return true
     */
    private static function deleteMenuFiles($menuInfo): bool
    {
        $menuNames = StrHelper::getMenuNames($menuInfo['controller']);

        // 后端文件
        $app_root = app()->getRootPath();
        $app_path = $app_root . 'app' . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR;

        $data['controller_base_file'] = $app_path . $menuNames['controller_base_path'];
        $data['controller_file'] = $app_path . $menuNames['controller_path'];

        $data['model_base_file'] = $app_path . $menuNames['model_base_path'];
        $data['model_file'] = $app_path . $menuNames['model_path'];

        $data['validate_base_file'] = $app_path . $menuNames['validate_base_path'];
        $data['validate_file'] = $app_path . $menuNames['validate_path'];

        // 删除文件
        foreach ($data as $file) {
            if (file_exists($file)) {
                unlink($file);
            }
        }
        return true;
    }


    /**
     * 当主键变更时，同步更新 dev_field 表中的对应记录
     * @param array $newMenuInfo
     * @param array $oldMenuInfo
     */
    private static function updatePrimaryKeyField(array $newMenuInfo, array $oldMenuInfo): void
    {
        $fieldInfo = FieldModel::where('menu_id', $newMenuInfo['id'])
            ->where('field', $oldMenuInfo['primary_key'])
            ->find();
        if ($fieldInfo) {
            $fieldInfo->field = $newMenuInfo['primary_key'];
            $fieldInfo->key = self::convertToLanguagePrefix($newMenuInfo['controller']) . '.field.' . $newMenuInfo['primary_key'];
            $fieldInfo->translations = ['zh-cn' => '编号', 'en' => 'ID'];
            $fieldInfo->save();
            // 同步多语言数据
            I18nService::asyncTranslationData($fieldInfo->key, $fieldInfo->translations);
        }
    }
}